/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#ifndef GIMI_SUBSCRIBERS_H
#define GIMI_SUBSCRIBERS_H

#include <set>
#include <map>
#include <deque>
#include <vector>
#include <string>

#include "gimiutils.h"

namespace gimi {

/**
 * \class KeepAlive
 * 
 * Container for keepalive-timing information.
 * Easy to check if it is time to do an action.
 * If interval is 0, 
 */
class KeepAlive {
  public:
    /**
     * Creates an inactive KeepAlive instance.
     */
    KeepAlive();
    
    /**
     * Creates an active KeepAlive instance with given interval.
     */
    KeepAlive(const int intervalMs);
    
    ~KeepAlive();
    
    /**
     * Checks if it is time to do new action.
     * 
     * \return True if nextrun had passed, false if nextrun had not passed.
     */
    bool isNextRunTime() const;
    
    /**
     * Checks if it is time to do new action, and updates nextrun-time
     * automatically to current time.
     * 
     * \return True if nextrun had passed and it was updated.
     * False if nextrun had not passed.
     */
    bool isNextRunTimeUpdate();
    
    /**
     * Writes a new nextrun-time based on current time.
     */
    void updateNextRunTime();
  
  private:
    bool active;
    gim::time interval;
    gim::time nextrun;
};
  
struct GIMIUnrepliedMessage
{
  GIMIUnrepliedMessage();
  GIMIUnrepliedMessage(const uint32_t mUid,
                       const gim::time &Time);
  ~GIMIUnrepliedMessage();

  bool operator== (const GIMIUnrepliedMessage& right) const;
  bool operator< (const GIMIUnrepliedMessage& right) const;
  bool operator> (const GIMIUnrepliedMessage& right) const;

  uint32_t messageUid;
  gim::time sentTime;
};

typedef std::set< GIMIUnrepliedMessage > t_unreplied;
typedef t_unreplied::iterator t_unrepliedIterator;
typedef t_unreplied::const_iterator t_unrepliedConstIterator;
 
class GIMISubscriber
{
  public:
    GIMISubscriber();
    GIMISubscriber(const int cId);
    ~GIMISubscriber();
    
    int getId() const;
    int getUnrepliedCount() const;
    
    /**
     * Used to query if this client is ready to receive a message. Clientid is
     * returned when client has <= maxUnsynced messages sent, after checking
     * messages that could have timeouted.
     *
     * \param[in] maxUnsynced Number of unsynced messages client can have to
     * send it this message. -1 sends message regardless of unsynced messages.
     * \param[in] timeouted Time when a message is considered timeouted.
     * \param[in] msgUid MessageUid for this message.
     * \param[in] sendTime Current time to compare to and to use as senttime.
     * \return 0 if client is not valid for sending, clientId otherwise.
     */
    int sendTo(const int maxUnsynced,
               const gim::time &timeouted,
               const uint32_t msgUid,
               const gim::time &sendTime);

    /**
     * Handles arrived reply. Removes timestamp with this msgUid from unreplied.
     */
    int handleReply(const uint32_t msgUid);

  private:
    int clientId;

    /**
     * Structures are sorted in ascending order by sentTime.
     * This speeds up finding timeouts, but finding messageUid is N.
     * (in reality, replies arrive pretty much synchronously and messageUid to
     * find is usually first in the set.)
     */
    t_unreplied unreplied;

};
  
typedef std::map< int, GIMISubscriber* > t_subscribers;
typedef t_subscribers::iterator t_subscribersIterator;
typedef t_subscribers::const_iterator t_subscribersConstIterator;

class GIMISubscriberContainer
{
  public:
    GIMISubscriberContainer(const std::string sDescr,
                            const std::string sLoc);
    
    GIMISubscriberContainer(const std::string sDescr,
                            const std::string sLoc,
                            const int keepAliveInterval);
                            
    ~GIMISubscriberContainer();

    const GIMISubscriberContainer& operator= (const GIMISubscriberContainer& right);
    GIMISubscriberContainer(const GIMISubscriberContainer& container);

    bool insert(const int clientId);
    int erase(const int clientId);
    int size() const;
    
    /**
     * Test if service has any subscribers.
     * 
     * \return True if no subscribers.
     */
    bool empty() const;
    
    std::string getDescription() const;
    std::string getLocation() const;
    

    /**
     * Returns array of subscribers that have less than maxSync messages unreplied
     * 
     * \return Size of array, < 0 for errors.
     */
    int getArray(int **subArray,
                 const int maxSync,
                 const gim::time &currentTime,
                 const gim::time &timeout,
                 const uint32_t msgUid);
    
    /**
     * Returns list of subscribers that have less than maxSync messages unreplied
     * 
     * \return Size of list, < 0 for errors.
     */
    int getSubscriberList(t_clientIdList &subscriberList,
                          const int maxSync,
                          const gim::time &currentTime,
                          const gim::time &timeout,
                          const uint32_t msgUid);

    int replyToClient(const uint32_t msgUid,
                      const int clientId);

  private:
    t_subscribers subscribers;
    std::string serviceDescription;
    std::string serviceLocation;
    
    // Made public for easier use. No need to duplicate KeepAlive's 
    // interface in this class
  public: 
    KeepAlive serviceKeepAlive;
};

// List of subscribers for a minor-typeid
typedef std::map< int, GIMISubscriberContainer* > t_servicesMinor;
typedef t_servicesMinor::iterator t_servicesMinorIterator;
typedef t_servicesMinor::const_iterator t_servicesMinorConstIterator;

// Minor-typeid's for a major-typeid.
typedef std::map< int, t_servicesMinor > t_services;
typedef t_services::iterator t_servicesIterator;
typedef t_services::const_iterator t_servicesConstIterator;

typedef std::pair< int, GIMISubscriber* > t_subscriberPair;
typedef std::pair< int, GIMISubscriberContainer* > t_subscribersPair;
typedef std::pair< int, t_servicesMinor > t_servicesMinorPair;

// Pair is majortypeid, minortypeid
typedef std::pair< int, int > t_serviceNumberPair;

// List of services containing only their majortype and minortype.
typedef std::vector< t_serviceNumberPair > t_serviceNumberList;
typedef t_serviceNumberList::iterator t_serviceNumberListIterator;
typedef t_serviceNumberList::const_iterator t_serviceNumberListConstIterator;

/**
 * \class GIMISubscribers
 * A container class for subscribers in different servicetypes.
 */
class GIMISubscribers
{
  public:
    GIMISubscribers();
    ~GIMISubscribers();


    /** Checks whether the specified major/minor pair is registered as
     * a service.
     *
     * \param[in] majorTypeId   MajorTypeId to check
     * \param[in] minorTypeId   MinorTypeId to check
     * \return                  'true' if the major/minor pair
     *                          is really registered. 'false' if it
     *                          not.
     *
     */
    bool isRegisteredType(const int majorTypeId,
                          const int minorTypeId);

    /**
     * Adds a servicetype to subscribercontainer.
     *
     * \param[in] majorTypeId MajorTypeId of service to add.
     * \param[in] minorTypeId MinorTypeId of service to add.
     * \param[in] serviceDescription Optional description of service.
     * \param[in] serviceLocation Optional location of service.
     * \param[in] keepAliveInterval Interval (milliseconds) for automatical
     * keepalive message. Interval 0 disables it.
     * \return False if a service with same id exists, true for success.
     */
    bool addServiceType(const int majorTypeId,
                        const int minorTypeId,
                        const std::string serviceDescription,
                        const std::string serviceLocation,
                        const int keepAliveInterval = 0);

    /**
     * Removes a servicetype from subscribercontainer.
     *
     * \param[in] majorTypeId MajorTypeId of service to remove.
     * \param[in] minorTypeId MinorTypeId of service to remove. Removes all
     * minor types if undefined.
     * \return False if service did not exist, true for success.
     */
    bool removeServiceType(const int majorTypeId,
                           const int minorTypeId = GIMI_IGNOREMINORTYPE);
    /**
     * Queries a list of services.
     * Adds to given vector, will not clear it.
     *
     * \param[out] serviceList List of found services.
     * \return Number of added services.
     */
    int listServices(t_serviceList &serviceList);
    
    /**
     * Queries a list of services that have keepalive set and need to be
     * updated. If service has no subscribers it does not need to be updated.
     * 
     * \param[out] serviceList List of found services
     * \return Number of found services.
     */
    int listServicesForKeepAlive(t_serviceNumberList &serviceList);
    
    /**
     * Adds a subscriber to servicetype.
     *
     * \param[in] clientId
     * \param[in] majorTypeId
     * \param[in] minorTypeId
     * \return
     */
    int addSubscriber(const int clientId,
                      const int majorTypeId,
                      const int minorTypeId);
    
    /**
     * Removes a subscriber from servicetype.
     *
     * \param[in] clientId
     * \param[in] majorTypeId
     * \param[in] minorTypeId
     * \return
     */
    int removeSubscriber(const int clientId,
                         const int majorTypeId,
                         const int minorTypeId);

    /**
     * Remove subscriber from all servicetypes.
     * Main use is to remove subscriber when he cannot be reached anymore.
     * \warning Runs through all services, if number of services is large,
     * a more efficient method should be devised.
     *
     * \param[in] clientId
     * \return Number of services the client was removed from.
     */
    int removeSubscriber(const int clientId);

    /**
     * Returns number of subscribers from given servicetype.
     *
     * \param[in] majorTypeId
     * \param[in] minorTypeId
     * \return GIMI_INVALIDTYPE, else number of subscribers.
     */
    int getSubscriberCount(const int majorTypeId,
                           const int minorTypeId) const;

    /**
     * Get subscribers from a servicetype. If maxUnsynced > -1, then msgUid and
     * timeout are also used.
     *
     * \param[out] subscriberArray
     * \param[out] arrayLength
     * \param[in] majorTypeId
     * \param[in] minorTypeId
     * \param[in] msgUid
     * \param[in] maxUnsynced Maximum number of unsynced messages for a client.
     * If client has more, it won't be added to subscriberArray.
     * Default is -1, which means this checks is disabled and all clients are
     * added to the array.
     * \param[in] timeout Timeout in milliseconds. Not used if maxUnsynced == -1.
     * Default is 500ms
     * \return GIMI_INVALIDTYPE, GIMI_ERROR, GIMI_OK
     */
    int getSubscribers(int **subscriberArray,
                       int *arrayLength,
                       const int majorTypeId,
                       const int minorTypeId,
                       const uint32_t msgUid,
                       const int maxUnsynced = -1,
                       const unsigned int timeout = 500);
    
    /**
     * Get subscriberlist arranged by id from a servicetype.
     * If maxUnsynced > -1, then msgUid and timeout are also used.
     * 
     * \param[out] subscriberList Subscribing clients are written here.
     * \param[in] majorTypeId
     * \param[in] minorTypeId
     * \param[in] msgUid
     * \param[in] maxUnsynced Maximum number of unsynced messages for a client.
     * If client has more, it won't be added to subscriberArray.
     * Default is -1, which means this checks is disabled and all clients are
     * added to the array.
     * \param[in] timeout Timeout in milliseconds. Not used if maxUnsynced == -1.
     * Default is 500ms
     * \return GIMI_INVALIDTYPE, GIMI_ERROR, GIMI_OK
     */
    int getSubscribers(t_clientIdList &subscriberList,
                       const int majorTypeId,
                       const int minorTypeId,
                       const uint32_t msgUid,
                       const int maxUnsynced = -1,
                       const unsigned int timeout = 500);

    /**
     * Reply has arrived, removes unreplied message from client's queue.
     * If client is found but not the msgUid, GIMI_EMPTY is returned.
     *
     * \param[in] msgUid
     * \param[in] majorTypeId
     * \param[in] minorTypeId
     * \param[in] clientId
     * \return GIMI_INVALIDTYPE, GIMI_INVALIDCLIENT, GIMI_ERROR, GIMI_EMPTY,
     * GIMI_OK
     */
    int replyToMessage(const uint32_t msgUid,
                       const int majorTypeId,
                       const int minorTypeId,
                       const int clientId);
    
  private:
    // Prevents use of copy constructors
    GIMISubscribers(const GIMISubscribers& subs);
    const GIMISubscribers& operator= (const GIMISubscribers& right);
    
    ownMutexHandle subscriberMutex; ///< Protects services.
    
    t_services services;
};

} // end namespace gimi

#endif // GIMI_SUBSCRIBERS_H
